package com.yycx.bpm.provider.controller;

import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yycx.bpm.provider.common.BaseFlowableController;
import com.yycx.bpm.provider.common.ResponseFactory;
import com.yycx.bpm.provider.common.cmd.ProcessModelDeployCmd;
import com.yycx.bpm.provider.common.cmd.ProcessModelSaveCmd;
import com.yycx.bpm.provider.common.cmd.SaveModelEditorCmd;
import com.yycx.bpm.provider.handler.ProcessModelListHandler;
import com.yycx.bpm.provider.model.ProcessModelRequest;
import com.yycx.bpm.provider.model.ProcessModelResponse;
import com.yycx.common.mybatis.model.ResultBody;
import com.yycx.common.utils.ApiAssert;
import com.yycx.common.base.utils.FlymeUtils;
import org.flowable.common.engine.api.FlowableObjectNotFoundException;
import org.flowable.common.engine.api.query.QueryProperty;
import org.flowable.engine.impl.ModelQueryProperty;
import org.flowable.engine.repository.Model;
import org.flowable.engine.repository.ModelQuery;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 模型管理
 */
@RestController
@RequestMapping("/bpm/model")
public class ProcessModelController extends BaseFlowableController {
    @Autowired
    protected ResponseFactory responseFactory;
    @Autowired
    protected ObjectMapper objectMapper;

    private static Map<String, QueryProperty> ALLOWED_SORT_PROPERTIES = new HashMap<>();

    static {
        ALLOWED_SORT_PROPERTIES.put("id", ModelQueryProperty.MODEL_ID);
        ALLOWED_SORT_PROPERTIES.put("category", ModelQueryProperty.MODEL_CATEGORY);
        ALLOWED_SORT_PROPERTIES.put("createTime", ModelQueryProperty.MODEL_CREATE_TIME);
        ALLOWED_SORT_PROPERTIES.put("key", ModelQueryProperty.MODEL_KEY);
        ALLOWED_SORT_PROPERTIES.put("lastUpdateTime", ModelQueryProperty.MODEL_LAST_UPDATE_TIME);
        ALLOWED_SORT_PROPERTIES.put("name", ModelQueryProperty.MODEL_NAME);
        ALLOWED_SORT_PROPERTIES.put("version", ModelQueryProperty.MODEL_VERSION);
        ALLOWED_SORT_PROPERTIES.put("tenantId", ModelQueryProperty.MODEL_TENANT_ID);
    }

    @GetMapping(value = "/page")
    public ResultBody page(@RequestParam(required = false) Map<String, String> params) {
        ModelQuery modelQuery = repositoryService.createModelQuery();
        String id = params.get("id");
        String category = params.get("category");
        String name = params.get("name");
        String key = params.get("key");
        String version = params.get("version");
        String latestVersion = params.get("latestVersion");
        String deploymentId = params.get("deploymentId");
        String deployed = params.get("deployed");
        String tenantId = params.get("tenantId");

        if (ObjectUtils.isNotEmpty(id)) {
            modelQuery.modelId(id);
        }
        if (ObjectUtils.isNotEmpty(category)) {
            modelQuery.modelCategoryLike(FlymeUtils.convertToLike(category));
        }
        if (ObjectUtils.isNotEmpty(name)) {
            modelQuery.modelNameLike(FlymeUtils.convertToLike(name));
        }
        if (ObjectUtils.isNotEmpty(key)) {
            modelQuery.modelKey(key);
        }
        if (ObjectUtils.isNotEmpty(version)) {
            modelQuery.modelVersion(FlymeUtils.convertToInteger(version));
        }
        if (ObjectUtils.isNotEmpty(latestVersion)) {
            boolean isLatestVersion = FlymeUtils.convertToBoolean(latestVersion);
            if (isLatestVersion) {
                modelQuery.latestVersion();
            }
        }
        if (ObjectUtils.isNotEmpty(deploymentId)) {
            modelQuery.deploymentId(deploymentId);
        }
        if (ObjectUtils.isNotEmpty(deployed)) {
            boolean isDeployed = FlymeUtils.convertToBoolean(deployed);
            if (isDeployed) {
                modelQuery.deployed();
            } else {
                modelQuery.notDeployed();
            }
        }
        if (ObjectUtils.isNotEmpty(tenantId)) {
            modelQuery.modelTenantId(tenantId);
        }
        return this.pageList(params, modelQuery, ProcessModelListHandler.class, ALLOWED_SORT_PROPERTIES);
    }

    @GetMapping(value = "/queryById")
    public ResultBody queryById(@RequestParam String id) throws UnsupportedEncodingException {
        Model model = getModelById(id);
        ProcessModelResponse modelResponse = responseFactory.createModelResponse(model);
        if (model.hasEditorSource()) {
            byte[] editorBytes = repositoryService.getModelEditorSource(model.getId());
            String processXml = new String(editorBytes, "UTF-8");
            modelResponse.setProcessXml(processXml);
        }
        return ResultBody.ok(modelResponse);
    }

    protected Model getModelById(String modelId) {
        Model model = repositoryService.getModel(modelId);
        if (model == null) {
            throw new FlowableObjectNotFoundException("No model found with id " + modelId);
        }
        return model;
    }

    protected void checkModelKeyExists(String modelKey) {
        long countNum = repositoryService.createModelQuery().modelKey(modelKey).count();
        if (countNum > 0) {
            throw new FlowableObjectNotFoundException("ModelKey already exists with id " + modelKey);
        }
    }


    @PostMapping(value = "/save")
    @Transactional(rollbackFor = Exception.class)
    public ResultBody save(ProcessModelRequest processModelRequest) {
        checkModelKeyExists(processModelRequest.getKey());
        Model model = repositoryService.newModel();
        model.setKey(processModelRequest.getKey());
        model.setName(processModelRequest.getName());
        model.setVersion(1);
        model.setMetaInfo(processModelRequest.getMetaInfo());
        model.setTenantId(processModelRequest.getTenantId());
        model.setCategory(processModelRequest.getCategory());
        repositoryService.saveModel(model);
        //保存流程文件
        managementService.executeCommand(new ProcessModelSaveCmd(model.getId(), processModelRequest.getProcessXml()));
        return ResultBody.ok(responseFactory.createModelResponse(model));
    }


    @PostMapping(value = "/update")
    @Transactional(rollbackFor = Exception.class)
    public ResultBody update(ProcessModelRequest modelRequest) {
        Model model = getModelById(modelRequest.getId());
        model.setKey(modelRequest.getKey());
        model.setName(modelRequest.getName());
        model.setMetaInfo(modelRequest.getMetaInfo());
        model.setTenantId(modelRequest.getTenantId());
        model.setCategory(modelRequest.getCategory());
        repositoryService.saveModel(model);
        //保存流程文件
        managementService.executeCommand(new ProcessModelSaveCmd(model.getId(), modelRequest.getProcessXml()));
        return ResultBody.ok(responseFactory.createModelResponse(model));
    }


    @DeleteMapping(value = "/delete")
    @Transactional(rollbackFor = Exception.class)
    public ResultBody delete(@RequestParam String ids, @RequestParam(required = false) boolean cascade) {
        if (ids == null || ids.trim().length() == 0) {
            ApiAssert.failure("ids can't be empty");
        }
        String[] idsArr = ids.split(",");
        for (String id : idsArr) {
            Model model = getModelById(id);
            List<Model> models = repositoryService.createModelQuery().modelKey(model.getKey()).list();
            for (Model deleteModel : models) {
                if (cascade && deleteModel.getDeploymentId() != null) {
                    repositoryService.deleteDeployment(deleteModel.getDeploymentId(), cascade);
                }
                repositoryService.deleteModel(deleteModel.getId());
            }
        }

        return ResultBody.ok();
    }


    @PostMapping(value = "/saveModel")
    @Transactional(rollbackFor = Exception.class)
    public ResultBody saveModel(ProcessModelRequest request) {
        managementService.executeCommand(new ProcessModelSaveCmd(request.getId(), request.getProcessXml()));
        return ResultBody.ok();
    }

    @PostMapping(value = "/deploy")
    @Transactional(rollbackFor = Exception.class)
    public ResultBody deployModel(ProcessModelRequest modelRequest) {
        managementService.executeCommand(new ProcessModelDeployCmd(modelRequest.getId()));
        return ResultBody.ok();
    }



    @PostMapping(value = "/import")
    @Transactional(rollbackFor = Exception.class)
    public ResultBody doImport(@RequestParam(required = false) String tenantId, HttpServletRequest request) throws IOException {
        if (!(request instanceof MultipartHttpServletRequest)) {
            throw new IllegalArgumentException("request must instance of MultipartHttpServletRequest");
        }
        MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
        if (multipartRequest.getFileMap().size() == 0) {
            throw new IllegalArgumentException("request file is empty");
        }
        MultipartFile file = multipartRequest.getFileMap().values().iterator().next();
        String fileName = file.getOriginalFilename();
        boolean isFileNameInValid =
                ObjectUtils.isEmpty(fileName) || !(fileName.endsWith(".bpmn20.xml") || fileName.endsWith(".bpmn") || fileName.toLowerCase().endsWith(".bar") || fileName.toLowerCase().endsWith(".zip"));
        if (isFileNameInValid) {
            throw new IllegalArgumentException("Request file must end with .bpmn20.xml,.bpmn|,.bar,.zip");
        }

        this.managementService.executeCommand(new SaveModelEditorCmd(null, tenantId, new String(file.getBytes(),
                "UTF-8")));

        return ResultBody.ok();
    }
}
